import { LockOutlined, UserOutlined } from '@ant-design/icons';
import { Button, Checkbox, Form, Input } from 'antd';
import { AuthenticationService, RememberMeService, Token, TokenService, User } from 'my-web-core/auth';
import { FormTextFieldValidator } from 'my-web-core/validators';
import { useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';

import AuthService from '@/features/authentication/services/auth-service';
import FormItemHelper, { FormItemValidationResultMapping } from '@/shared/models/form-item-helper';
import AuthenticationSlice from '@/shared/redux/authentication-slice';
import Hook from '@/shared/redux/hook';

import '@/features/authentication/components/Login.css';

function Login() {
  const currentUsername = Hook.useAppSelector<string | null>(
    (state) => state.authentication.currentUsername,
  );
  const dispatch = Hook.useAppDispatch();
  const navigate = useNavigate();
  const [searchParams] = useSearchParams();

  // For validation purpose.
  const [validationResultMapping, setValidationResultMapping] =
    useState<FormItemValidationResultMapping>({
      username: {
        name: 'username',
        label: '用户名',
      },
      password: {
        name: 'password',
        label: '密码',
      },
    });

  async function login(username: string, password: string, rememberMe: boolean): Promise<void> {
    const res = await new AuthService().login(username, password);

    if (res?.accessToken) {
      RememberMeService.rememberMe = rememberMe;

      // Gets the server data to user instance.
      const user = User.fromJSON(res);

      const authenticationService = new AuthenticationService();

      // Saves the user data retrieved from server.
      authenticationService.saveUserInfo(user);

      // Saves current username for next logic form auto fill input.
      authenticationService.saveCurrentUsername();

      // Gets the server data to token instance.
      const token = await Token.fromServerJSON(res);

      // Saves the token data retrieved from server.
      TokenService.save(token);

      // Notifies change.
      dispatch(AuthenticationSlice.changeIsAuthenticated(AuthenticationService.isAuthenticated));
      dispatch(AuthenticationSlice.changeCurrentUser(AuthenticationService.currentUser));
      dispatch(
        AuthenticationSlice.changeCurrentUsername(AuthenticationService.currentUser!.username),
      );

      const redirectTo = searchParams.get('redirectTo') ?? '/home';

      // Once user logged in the login route will be replaced in the history stack.
      navigate(redirectTo, { replace: true });
    } else {
      validationResultMapping.password = Object.assign(
        validationResultMapping.password,
        FormItemHelper.getErrorValidationResult('用户名或密码错误'),
      );

      // To make setState work we have to create a new object with spreaded properties.
      setValidationResultMapping({ ...validationResultMapping });
    }
  }

  /**
   * Handles input value change for validation purpose.
   */
  function handleChange(e: any): void {
    const key = e.target.id;
    const value = e.target.value;

    const validator = new FormTextFieldValidator(validationResultMapping[key].validatorArguments);
    validator.args.label = validationResultMapping[key].label!;
    validator.args.value = value;

    // Merges object changes.
    Object.assign(validationResultMapping[key], FormItemHelper.validate(validator));

    // To make setState work we have to create a new object with spreaded properties.
    setValidationResultMapping({ ...validationResultMapping });
  }

  /**
   * When form submitted.
   */
  function onFinish(values: any): void {
    for (const key in validationResultMapping) {
      const validator = new FormTextFieldValidator(validationResultMapping[key].validatorArguments);
      validator.args.label = validationResultMapping[key].label!;
      validator.args.value = values[validationResultMapping[key].name!];

      // Merges two objects.
      Object.assign(validationResultMapping[key], FormItemHelper.validate(validator));
    }

    // To make setState work we have to create a new object with spreaded properties.
    setValidationResultMapping({ ...validationResultMapping });

    if (FormItemHelper.isFormValidationSuccessful(validationResultMapping)) {
      login(values.username, values.password, values.remember);
    }
  }

  return (
    <div className="Login-container">
      <div className="Login-form-container">
        <h2 className="Login-title">平台管理系统</h2>
        <Form
          initialValues={{
            username: currentUsername,
            remember: RememberMeService.rememberMe,
          }}
          onFinish={onFinish}
        >
          <Form.Item
            name={validationResultMapping.username.name}
            validateStatus={validationResultMapping.username.validateStatus}
            help={validationResultMapping.username.errorMsg}
          >
            <Input
              prefix={<UserOutlined className="site-form-item-icon" />}
              placeholder={validationResultMapping.username.label}
              onChange={handleChange}
            />
          </Form.Item>
          <Form.Item
            name={validationResultMapping.password.name}
            validateStatus={validationResultMapping.password.validateStatus}
            help={validationResultMapping.password.errorMsg}
          >
            <Input
              prefix={<LockOutlined className="site-form-item-icon" />}
              type="password"
              placeholder={validationResultMapping.password.label}
              onChange={handleChange}
            />
          </Form.Item>
          <Form.Item>
            <Form.Item name="remember" valuePropName="checked" noStyle>
              <Checkbox>记住我</Checkbox>
            </Form.Item>
          </Form.Item>
          <Form.Item>
            <Button type="primary" htmlType="submit" className="Login-form-button">
              登录
            </Button>
          </Form.Item>
        </Form>
      </div>
    </div>
  );
}

export default Login;
